# @Time : 2024-11-29 13:39
# @Author : Fioman 
# @Phone : 13149920693
"""
和web处理相关,启动浏览器,还有向web发送消息的接口
"""
import os
import subprocess
import sys
import winreg

import bottle
import bottle.ext.websocket as wbs
import gevent

import app_config
from common.produce_logger import debug_produce

socketPort = 9099


def get_real_path(webDir):
    """
    获取web的真实目录,主要一个是开发模式,一个是生产打包后的模式
    :param webDir:
    :return:
    """
    # 主要是打包之后的路径会存放到sys._MEIPASS目录下,就相当于是打包之后的项目的根目录
    # 而web是放到这个根目录下面的
    if getattr(sys, 'frozen', False):
        return os.path.join(sys._MEIPASS, webDir)
    else:
        return os.path.join(app_config.baseDir, webDir)


def build_url_from_dict(page):
    """
    当page是字典的时候构建
    :param page:
    :return:
    """
    scheme = page.get('scheme', 'http')
    host = page.get('host', 'localhost')
    port = page.get('port', 8000)
    path = page.get('path', '')
    return '%s://%s:%d/%s' % (scheme, host, port, path)


def build_url_from_string(page, options):
    """
    当page是字符串的时候构建
    :param page:
    :param options:
    :return:
    """
    baseUrl = 'http://%s:%d/' % (options['host'], options['port'])
    return baseUrl + page


def build_urls(startUrls, options):
    """
    构建urls
    :param startUrls:
    :param options:
    :return:
    """
    urlList = []
    for page in startUrls:
        if isinstance(page, dict):
            url = build_url_from_dict(page)
        else:
            url = build_url_from_string(page, options)
        urlList.append(url)
    return urlList


def get_chrome_abs_path():
    if sys.platform in ["win32", "win64"]:
        regPath = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
        for installType in winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE:
            try:
                regKey = winreg.OpenKey(installType, regPath, 0, winreg.KEY_READ)
                chromePath = winreg.QueryValue(regKey, None)
                regKey.Close()
            except WindowsError:
                # 找不到的时候会捕获这个异常,当找到的时候就不会捕获这个异常,就会走else分支.
                chromePath = None
            else:  # else分支的作用就是没有发生异常,找到了这个路径,就直接弹出来了.
                break
        return chromePath


def open_chrome(startUrls, options):
    """
    打开浏览器
    :param startUrls:首页路由
    :param options: 打开的一些参数,"--start-maximized": 最大化   --kiosk: 全屏启动
    :return:
    """
    startUrls = build_urls(startUrls, options)
    chromePath = get_chrome_abs_path()
    if chromePath is not None:
        if options['mode'] == "chrome-app":
            for url in startUrls:
                subprocess.Popen([chromePath, '--app={}'.format(url)] +
                                 options["chromeFlags"] + ["--kiosk"],
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
        else:
            args = options["chromeFlags"] + startUrls + ["--start-maximized"]
            subprocess.Popen([chromePath, "--new-window "] + args,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)


def start_service(*startUrls, **kwargs):
    """
    启动浏览器和后端服务
    :param startUrls:
    :param kwargs:
    :return:
    """
    options = kwargs.pop("options", {})
    gevent.spawn(run_bottle, options)
    open_chrome(startUrls, options)


def run_bottle(options):
    """
    启动bottle服务,就是启动websocket服务
    :param options:
    :return:
    """
    return bottle.run(host=options['host'],
                      port=options['port'],
                      server=wbs.GeventWebSocketServer,
                      quiet=True)


def repeat_send(msg):
    """
    重复的发送N次消息,直到消息发送成功为止
    :param msg:
    :return:
    """
    attemptTimes = 20
    assert isinstance(msg, type(""))
    for _, ws in app_config.websockets:
        for attempt in range(attemptTimes):
            try:
                ws.send(msg)
                break
            except Exception as e:
                ws.send(msg)  # 如果出现异常就重新发送.
                print("in repeat_send() error: {}".format(str(e)))
                debug_produce("in repeat_send() error: {}".format(str(e)))
                gevent.sleep(0.01)


def web_msg_send_loop():
    """
    向web前端发送消息,这里采用的是一个循环模式,就是会不停的扫描队列,然后有消息就发送
    :return:
    """
    try:
        while app_config.isMainAlive:
            if not app_config.webMsgQueue.empty():
                msg = app_config.webMsgQueue.get()
                debug_produce(f"msgSend: {msg}")
                repeat_send(msg)
            gevent.sleep(0.1)
    except KeyboardInterrupt:
        app_config.isMainAlive = False
    except Exception as e:
        debug_produce(f"in web_msg_send_loop() error: {e}")


def start_web_service():
    """
    启动浏览器和后台服务
    :return:
    """
    if app_config.isDevelop:
        directory = "src"
        mode = "chrome"
        page = {
            "port": 8000,
            "path": "user/login"
        }
    else:
        directory = "web"
        mode = "chrome-app"
        page = "index.html"

    app_config.webPath = get_real_path(directory)
    options = {
        "mode": mode,
        "port": socketPort,
        "host": "localhost",
        "chromeFlags": [],
    }
    # 启动浏览器服务
    start_service(page, options=options)
    # 启动后台发送消息的服务
    web_msg_send_loop()
